-
Notifications
You must be signed in to change notification settings - Fork 22
Assignment 1 #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: Assignment1
Are you sure you want to change the base?
Assignment 1 #19
Conversation
…U-CSCD371-2025-Fall into assignment-1-colab
…U-CSCD371-2025-Fall into assignment-1-colab
…estion generation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds AI-generated trivia questions to the Princess Bride trivia game using OpenAI's API. After completing the preset questions, the game continues with dynamically generated questions until the user types 'exit'.
- Integrated OpenAI API to generate additional Princess Bride trivia questions
- Added continuous gameplay loop with exit condition
- Fixed integer division bug in percentage calculation
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
File | Description |
---|---|
Program.cs | Added OpenAI integration, continuous gameplay loop, and fixed percentage calculation |
PrincessBrideTrivia.csproj | Added OpenAI package dependency |
ProgramTests.cs | Added basic tests for AI question generation functionality |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
{ | ||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); | ||
Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); | ||
Assert.HasCount(4, q.Answers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert.HasCount is not a valid MSTest assertion method. Use Assert.AreEqual(4, q.Answers.Length) instead.
Assert.HasCount(4, q.Answers); | |
Assert.AreEqual(4, q.Answers.Length); |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may have been the standard previously, but with the MSTests that were provided which are more current than the old standards, this is no longer the case, and .HasCount is actually correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super cool extra credit. Though I cannot say how well the AI semantics play (I cannot be bothered getting an API key). I'm confident the try,catch,async and awaits will be functional.
} | ||
|
||
public static bool AskQuestion(Question question) | ||
public static (bool, bool) AskQuestion(Question question) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could replace the tuple with [flag] enum for better code clarity.
EX:
[Flags] public enum SorryImBadAtNames: byte { None = 0, AnswerdCorrectly = 1, QuitProgram = 2, };
Then you can use the objectEnum.HasFlag(FLAG);
to figure out the response. More info at link:
https://learn.microsoft.com/en-us/dotnet/api/system.enum.hasflag?view=net-9.0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementing enum would be a lot messier and a less concise. However, dealing with both the correct answers and quitting the program in one function is a good suggestion and will be implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assignment Details
Pull Request title includes "Assignment 1" in the title ✔
Partner has also made a commit ✔
Pull Request targets Assignment 1 branch ✔
Issue 1: Application no longer crashes ✔
Issue 2: Unit test properly passes ✔
Extra Credit
Come up with a new feature for the application
Issue 3: Add a new feature request implemented ✔
Issue 3: Feature request unit tested ✔
Very impressive in my opinion! The AI feature is super cool and you guys definitely have some know how! great work
string answer2 = lines[lineIndex + 2]; | ||
string answer3 = lines[lineIndex + 3]; | ||
Question question = new() { Text = lines[lineIndex], Answers = new string[3], CorrectAnswerIndex = lines[lineIndex + 4] }; | ||
question.Answers[0] = lines[lineIndex + 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how in this section you condensed the code and made it more streamlined. However, I personally prefer the readability of the previous version. Since I don't see this contradicting any guidelines this is purely a stylistic opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd personally chose to stick with the streamlined code because it’s cleaner and easier to maintain. It does the same thing as before but with less repetition, which reduces the chance of errors if future maintenance was to become a concern.
if (string.IsNullOrWhiteSpace(apiKey)) | ||
throw new ArgumentException("API key is required.", nameof(apiKey)); | ||
|
||
if (choices is < 3 or > 6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very much grasping for straws here cause your code is so spot on! perhaps you should avoid making the bounds magic numbers and instead having the answer bounds be variables or consts. This would add versatility if the program ever was to be updated with more options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback! In this case, I chose to leave the bounds as-is since the program is intentionally simple and unlikely to change. Introducing constants or variables here wouldn’t add much value for this particular use case, but I appreciate the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good discussion you two :) This is definitely what we're looking for here. Respectful discussion and explanation of your reasoning.
Side note: I would tend to agree with William here, though. I think it'd be best practice to set them in local or global fields, for future extensibility, like William said. Always sticking to best practice builds up good habits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assignment Details
Pull Request title includes "Assignment 1" in the title ✔
Partner has also made a commit ✔
Pull Request targets Assignment 1 branch ✔
Issue 1: Application no longer crashes ✔
Issue 2: Unit test properly passes ✔
Extra Credit
Come up with a new feature for the application
Issue 3: Add a new feature request implemented ✔
Issue 3: Feature request unit tested ✔
Cool feature update!
…U-CSCD371-2025-Fall into assignment-1-colab
…e Assert.HasCount(...) instead of Assert.AreEqual(..., collection.Length).
…n the same method in order to have more readable code.
… of the program. I changed it to the very beginning so as to be more user friendly. I also fixed the grades to display properly- whether you quit early, or if you finish everything correctly.
SummarySummary
CoveragePrincessBrideTrivia - 22.1%
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assignment Details
- Pull Request title includes "Assignment 1" in the title ✔
- Partner has also made a commit ✔
- Pull Request targets Assignment 1 branch ✔
- Issue 1: Application no longer crashes ✔
- Issue 2: Unit test properly passes ✔
Extra Credit
- Come up with a new feature for the application
- Issue 3: Add a new feature request implemented ✔
- Issue 3: Feature request unit tested ✔-ish
Honestly, phenomenal extra credit. That's some cool stuff. I tested it out with an OpenAI key and it worked great. The only note I'd make is change how it's tested - but we're not at a point in class that we've gone over that yet. See my comment for some information on how to go about that.
Edit: though I will say, make sure to get all the additional functionality tested and implemented well. I assume a coding assistant was used, solely because of the extra comments with check marks. Definitely encourage that if you're understanding what it's doing and can do it yourself/already did first. But quality testing and implementation comes before extra functionality with lesser quality.
[TestMethod] | ||
public async Task GenerateQuestions_Success() | ||
{ | ||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); | ||
|
||
if (apiKey == null) | ||
{ | ||
return; // test cannot run without the API key | ||
} | ||
|
||
Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); | ||
Assert.IsNotNull(q); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably haven't gotten to the point in class that we could test this easier, but relying on an external API for testing and CI/CD will make the test flaky (sometimes pass, sometimes fail, sometimes won't even test functionality - as it does here, and ultimately passes because no exceptions are thrown).
If you want to look ahead, Moq is a great way to mock implementations to insert "mocked" implementation for testing purposes. It's used mostly on interfaces and abstract classes, so you'd have to rearrange your architecture.
catch (InvalidOperationException ex) | ||
{ | ||
Console.WriteLine(ex.Message); | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a feeling if an exception was thrown here, it wouldn't be good to continue on. That'd signal there's something wrong with the OpenAI API or your TriviaGenerator code, and it probably wouldn't fix itself between iterations. Another dangerous thought is, if it was left to run by itself and kept running into this exception, it could run down your OpenAI tokens and increase your budget, since the exception can occur before ever asking for user input.
} | ||
Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, questions.Length) + " correct"); | ||
|
||
// ✅ If user answered all file questions and we are NOT running AI loop, print score now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments (especially, I assume, LLM comments 😉) that don't add clarity to the reader is against guidelines.
} | ||
|
||
public static bool AskQuestion(Question question) | ||
public static (bool answeredCorrectly, bool quitProgram) AskQuestion(Question question) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend against returning Tuples, as I think Mark said on Thursday. Could use an out parameter or maybe restructure logic around determining quitting.
if (string.IsNullOrWhiteSpace(apiKey)) | ||
throw new ArgumentException("API key is required.", nameof(apiKey)); | ||
|
||
if (choices is < 3 or > 6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good discussion you two :) This is definitely what we're looking for here. Respectful discussion and explanation of your reasoning.
Side note: I would tend to agree with William here, though. I think it'd be best practice to set them in local or global fields, for future extensibility, like William said. Always sticking to best practice builds up good habits.
Difficulty: hard. | ||
"""; | ||
|
||
var completion = await client.CompleteChatAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd lean away from using 'var' here. Mark's guidelines advise against it here: AVOID using implicitly typed local variables ( var ) unless the data type of the assigned value is obvious
.
throw new InvalidOperationException("Model returned empty content."); | ||
|
||
// Strict JSON parse | ||
var question = JsonSerializer.Deserialize<Question>(json, new JsonSerializerOptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, although if the reader has experience with JsonSerializer, they could determine the type. It's not immediately obvious though.
if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON."); | ||
if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing."); | ||
if (q.Answers is null || q.Answers.Length != expectedChoices) | ||
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); | ||
|
||
if (q.Answers.Any(string.IsNullOrWhiteSpace)) | ||
throw new InvalidOperationException("One or more answers are empty."); | ||
|
||
if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) | ||
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); | ||
|
||
if (idx < 0 || idx >= q.Answers.Length) | ||
throw new InvalidOperationException("CorrectAnswerIndex out of range."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON."); | |
if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing."); | |
if (q.Answers is null || q.Answers.Length != expectedChoices) | |
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); | |
if (q.Answers.Any(string.IsNullOrWhiteSpace)) | |
throw new InvalidOperationException("One or more answers are empty."); | |
if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) | |
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); | |
if (idx < 0 || idx >= q.Answers.Length) | |
throw new InvalidOperationException("CorrectAnswerIndex out of range."); | |
if (q is null) | |
{ | |
throw new InvalidOperationException("Failed to parse the model's JSON."); | |
} | |
if (string.IsNullOrWhiteSpace(q.Text)) | |
{ | |
throw new InvalidOperationException("Question text missing."); | |
} | |
if (q.Answers is null || q.Answers.Length != expectedChoices) | |
{ | |
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); | |
} | |
if (q.Answers.Any(string.IsNullOrWhiteSpace)) | |
{ | |
throw new InvalidOperationException("One or more answers are empty."); | |
} | |
if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) | |
{ | |
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); | |
} | |
if (idx < 0 || idx >= q.Answers.Length) | |
{ | |
throw new InvalidOperationException("CorrectAnswerIndex out of range."); | |
} |
Might just be a personal thing, but it's not immediately easy to read through this block. Even though certain statements and blocks can be abbreviated, I'd stick towards making them clearer. Although it's a little more typing, I'd do the suggestion above. Or even having the first two if statements not be one line total.
Here's what Mark's guidelines say about it: AVOID omitting braces, except for the simplest of single-line if statements.
# Visual Studio Version 17 | ||
VisualStudioVersion = 16.0.29215.179 | ||
VisualStudioVersion = 17.14.36518.9 d17.14 | ||
MinimumVisualStudioVersion = 10.0.40219.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be here. Be sure to review all changes before staging/committing.
@chanderlud @JosephPotapenko make sure to respond to and resolve all comments (even Copilot's review). |
I worked with @JosephPotapenko on this assignment.
For extra credit, we implemented an AI trivia question generator that uses the OpenAI API to generate more questions once the preset ones run out. In order to use this feature, the
OPENAI_API_KEY
environmental variable must be set to a valid API key.